package com.wdy.golden.tinysql.proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import com.wdy.golden.tinysql.util.StringUtil;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author Dongye_Wu
 * @version: 1.0.0
 * @Description: 用于Spring动态注入自定义接口
 * @date 2020年4月10日 下午11:19:28
 */

@Component
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {
	private static final Logger log = LoggerFactory.getLogger(ServiceBeanDefinitionRegistry.class);

	private ResourcePatternResolver resourcePatternResolver;

	private ApplicationContext applicationContext;

	private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

	private MetadataReaderFactory metadataReaderFactory;


	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	private Environment getEnvironment() {
		return applicationContext.getEnvironment();
	}

	protected String resolveBasePackage(String basePackage) {
		return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
	}

	/**
	 * 根据包路径获取包及子包下的所有类
	 * 
	 * @param basePackage basePackage
	 * @return Set<Class<?>> Set<Class<?>>
	 */
	private Set<Class<?>> scannerPackages(String basePackage) {
		Set<Class<?>> set = new LinkedHashSet<>();
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
		log.debug("[Tinysql] packageSearchPath is {}", packageSearchPath);
		try {
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			for (Resource resource : resources) {
				log.debug("[Tinysql] resource url is {}", resource.getURL().getPath());
				if (resource.isReadable()) {
					log.debug("[Tinysql] {} is readable and on going.", resource.getURL().getPath());
					MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
					String className = metadataReader.getClassMetadata().getClassName();
					Class<?> clazz;
					try {
						clazz = Class.forName(className);
						set.add(clazz);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
				}else {
					log.warn("[Tinysql] {} is not readable.", resource.getURL().getPath());
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return set;
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
	}
	

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		String mappingInterfacePackage = getEnvironment().getProperty("tinysql.mappingInterfacePackage");
		if(StringUtil.isEmpty(mappingInterfacePackage)) {
			log.error("[Tinysql] error happened when read config property tinysql.mappingInterfacePackage: {}" + mappingInterfacePackage);
			Assert.notNull(mappingInterfacePackage, "[Tinysql] error happened when read config property tinysql.mappingInterfacePackage");
		}
		
		Set<Class<?>> beanClazzs = scannerPackages(mappingInterfacePackage);
		for (Class<?> beanClazz : beanClazzs) {
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
			GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

			// 在这里，我们可以给该对象的属性注入对应的实例。
			// 比如mybatis，就在这里注入了dataSource和sqlSessionFactory，
			// 注意，如果采用definition.getPropertyValues()方式的话，
			// 类似definition.getPropertyValues().add("interfaceType", beanClazz);
			// 则要求在FactoryBean（本应用中即ServiceFactory）提供setter方法，否则会注入失败
			// 如果采用definition.getConstructorArgumentValues()，
			// 则FactoryBean中需要提供包含该属性的构造方法，否则会注入失败
			definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

			// 注意，这里的BeanClass是生成Bean实例的工厂，不是Bean本身。
			// FactoryBean是一种特殊的Bean，其返回的对象不是指定类的一个实例，
			// 其返回的是该工厂Bean的getObject方法所返回的对象。
			definition.setBeanClass(ServiceFactory.class);

			// 这里采用的是byName方式注入，类似的还有byType等
			definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
			registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
			log.debug("[Tinysql] autowire bean {} is finished.", beanClazz.getSimpleName());
		}
	}
}
